%% @author wenqi_zheng
%% @doc @todo Add description to tcp_listener.


-module(tcp_listener).
-behaviour(gen_server).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).

%% ====================================================================
%% API functions
%% ====================================================================
-export([start_link/2]).



%% ====================================================================
%% Behavioural functions
%% ====================================================================
-record(state, {
				listener,
				acceptor,
				module
				}).



start_link(Port, Module) when is_integer(Port), is_atom(Module) ->
	gen_server:start_link({local, ?MODULE}, ?MODULE, [Port, Module], []).

%% init/1
%% ====================================================================
%% @doc <a href="http://www.erlang.org/doc/man/gen_server.html#Module:init-1">gen_server:init/1</a>
-spec init(Args :: term()) -> Result when
	Result :: {ok, State}
			| {ok, State, Timeout}
			| {ok, State, hibernate}
			| {stop, Reason :: term()}
			| ignore,
	State :: term(),
	Timeout :: non_neg_integer() | infinity.
%% ====================================================================
init([Port, Module]) ->
	process_flag(trap_exit, true),
	Opts = [binary, {packet, 2}, {reuseaddr, true},
			{keepalive, true}, {backlog, 30}, {active, false}],
	%%　使用 gen_tcp 模块启动套接字监听，这是一个阻塞动作
	case gen_tcp:listen(Port, Opts) of
		{ok, Listen_socket} ->
			%% 创建监听成功返回监听socket
			%% 创建第一个接受连接的进程
			%% prim_inet:async_accept/2开启异步监听
			%% 之后有客户端连接时会向此进程发送一个异步消息inet_async到进程消息队列
			%% Ref 存储接受进程的引用
			{ok, Ref} = prim_inet:async_accept(Listen_socket, -1),
			{ok, #state{listener = Listen_socket, acceptor = Ref, module = Module}};
		{error, Reason} ->
			{stop, Reason}
	end.



%% handle_call/3
%% ====================================================================
%% @doc <a href="http://www.erlang.org/doc/man/gen_server.html#Module:handle_call-3">gen_server:handle_call/3</a>
-spec handle_call(Request :: term(), From :: {pid(), Tag :: term()}, State :: term()) -> Result when
	Result :: {reply, Reply, NewState}
			| {reply, Reply, NewState, Timeout}
			| {reply, Reply, NewState, hibernate}
			| {noreply, NewState}
			| {noreply, NewState, Timeout}
			| {noreply, NewState, hibernate}
			| {stop, Reason, Reply, NewState}
			| {stop, Reason, NewState},
	Reply :: term(),
	NewState :: term(),
	Timeout :: non_neg_integer() | infinity,
	Reason :: term().
%% ====================================================================
handle_call(Request, From, State) ->
    Reply = ok,
    {reply, Reply, State}.


%% handle_cast/2
%% ====================================================================
%% @doc <a href="http://www.erlang.org/doc/man/gen_server.html#Module:handle_cast-2">gen_server:handle_cast/2</a>
-spec handle_cast(Request :: term(), State :: term()) -> Result when
	Result :: {noreply, NewState}
			| {noreply, NewState, Timeout}
			| {noreply, NewState, hibernate}
			| {stop, Reason :: term(), NewState},
	NewState :: term(),
	Timeout :: non_neg_integer() | infinity.
%% ====================================================================
handle_cast(Msg, State) ->
    {noreply, State}.


%% handle_info/2
%% ====================================================================
%% @doc <a href="http://www.erlang.org/doc/man/gen_server.html#Module:handle_info-2">gen_server:handle_info/2</a>
-spec handle_info(Info :: timeout | term(), State :: term()) -> Result when
	Result :: {noreply, NewState}
			| {noreply, NewState, Timeout}
			| {noreply, NewState, hibernate}
			| {stop, Reason :: term(), NewState},
	NewState :: term(),
	Timeout :: non_neg_integer() | infinity.
%% ====================================================================
handle_info({inet_async, ListSock, Ref, {ok, CliSocket}},
			 #state{listener=ListSock, acceptor=Ref, module=Module} = State) ->
	try 
		case set_sockopt(ListSock, CliSocket) of
			ok -> ok;
			{error, Reason} -> exit({set_sockopt, Reason})
		end,
		
		%% 接收新的客户端连接，启动一个新的客户端状态机进程，动态添加到 tcp_client_sup 客户端监控树
		{ok, Pid} = tcp_server_app:start_client(),
		
		%% 绑定 CliSocet 到客户端进程 Pid, 这样CliSocket接收数据都会被转化成Pid代表进程的邮箱消息
		gen_tcp:controlling_process(CliSocket, Pid),
		
		
		Module:set_socket(Pid, CliSocket),
		
		%% 重新设置异步监听下一个客户端连接的消息，设置新的监听引用
		%% 必须重新设置才能监听到 {inet_async,S,Ref,Status} 消息
		case prim_inet:async_accept(ListSock, -1) of
			{ok,    NewRef} -> ok;
			{error, NewRef} -> exit({async_accept, inet:format_error(NewRef)})
		end,
		
		%% 更新新的监听引用
		{noreply, State#state{acceptor=NewRef}}
	
	catch exit:Why ->
			  error_logger:error_msg("Error in async accept: ~p. ~n", [Why]),
			  {stop, Why, State}
	end;
		
handle_info(Info, State) ->
    {noreply, State}.


%% terminate/2
%% ====================================================================
%% @doc <a href="http://www.erlang.org/doc/man/gen_server.html#Module:terminate-2">gen_server:terminate/2</a>
-spec terminate(Reason, State :: term()) -> Any :: term() when
	Reason :: normal
			| shutdown
			| {shutdown, term()}
			| term().
%% ====================================================================
terminate(Reason, State) ->
    ok.


%% code_change/3
%% ====================================================================
%% @doc <a href="http://www.erlang.org/doc/man/gen_server.html#Module:code_change-3">gen_server:code_change/3</a>
-spec code_change(OldVsn, State :: term(), Extra :: term()) -> Result when
	Result :: {ok, NewState :: term()} | {error, Reason :: term()},
	OldVsn :: Vsn | {down, Vsn},
	Vsn :: term().
%% ====================================================================
code_change(OldVsn, State, Extra) ->
    {ok, State}.


%% ====================================================================
%% Internal functions
%% ====================================================================

%% 设置客户端socket的参数选项，只是简单的复制了监听服务器的配置选项
set_sockopt(ListSock, CliSocket) ->
	true = inet_db:register_socket(CliSocket, inet_tcp),
	case prim_inet:getopts(ListSock, [active, nodelay, keepalive, delay_send, priority, tos]) of
		{ok, Opts} ->
			case prim_inet:setopts(CliSocket, Opts) of
				ok    -> ok;
				Error -> gen_tcp:close(CliSocket), Error
			end;
		Error ->
			gen_tcp:close(CliSocket), Error
	end.

